package org.ying.container;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.ying.container.annotation.Panda;
import org.ying.container.annotation.PandaRoom;
import org.ying.container.aop.Aspect;
import org.ying.container.aop.AspectInfo;
import org.ying.container.aop.PointCut;
import org.ying.container.aop.ProxyFactory;

import javax.inject.Inject;
import javax.inject.Named;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class PandaBreedBase implements Container {

    ProxyFactory proxyFactory = ProxyFactory.create();
    /**
     * 名称和对应的实例
     */
    private final Map<String,Object> pandaByNameMap = new ConcurrentHashMap<>(256);
    /**
     * 类型和对应的实例,存在同类型而名称不同的实例
     */
    private final Map<Class,Map<String,Object>> pandasByClassMap = new ConcurrentHashMap<>(256);
    /**
     * 切面信息
     */
    private Map<String,PointCut> aspectInfoMap  = new HashMap<>();
    /**
     * 需要被代理的的目标和切面的信息
     */
    private Map<String,List<String>> targetAspectMap = new HashMap<>();
    /**
     * 实例胚胎,实例的实例化就是靠这个里面的数据
     */
    private final Map<String, PandaEmbryo> pandaEmbryoByNameMap = new ConcurrentHashMap<>(256);
    /**
     * 类型对应的实例名称,方便的一道存储信息
     */
    private final Map<Class,List<String>> pandaNamesByClassMap = new ConcurrentHashMap<>(256);
    /**
     * 正在实例化的胚胎
     */
    private final Set<PandaEmbryo> pandaBreedingSet = Collections.synchronizedSet(new HashSet<>());

    @Override
    public Object getPanda(String name) throws PandaException {
        Assert.notBlank(name);
        Object panda = pandaByNameMap.get(name);
        if (panda != null){
            return panda;
        }
        PandaEmbryo pandaEmbryo = pandaEmbryoByNameMap.get(name);
        if (pandaEmbryo == null){
            throw new PandaException(StrFormatter.format("no pandaOosperm found with name {}",name));
        }
        if (pandaBreedingSet.contains(pandaEmbryo)){
            throw new PandaException(StrFormatter.format("circle dependency pandaOosperm found with name {}",name));
        }
        pandaBreedingSet.add(pandaEmbryo);
        Object breed = doBreed(pandaEmbryo);
        pandaBreedingSet.remove(pandaEmbryo);
        return breed;
    }

    @Override
    public <T> T getPanda(String name, Class<T> requiredType) throws PandaException {
        if (StrUtil.isBlank(name) || ObjectUtil.isNull(requiredType)){
            throw new PandaException("name , class must be not null");
        }
        Object panda = getPanda(name);
        if (requiredType.isInstance(panda)){
            return (T)panda;
        }
        throw new PandaException(StrFormatter.format("no matched panda name:{},class:{}",name,requiredType));
    }

    @Override
    public <T> T getPanda(Class<T> requiredType) throws PandaException {
        Assert.notNull(requiredType);
        List<String> pandaNameList = pandaNamesByClassMap.get(requiredType);
        if (pandaNameList == null){
            throw new PandaException(StrFormatter.format("no matched panda found with class:{}",requiredType));
        }
        if (pandaNameList.size() > 1){
            throw new PandaException(StrFormatter.format("more than 1 panda found with class:{}",requiredType));
        }
        Object panda = getPanda(pandaNameList.get(0));
        return (T)panda;
    }

    @Override
    public <T> Map<String, T> getPandas(Class<T> requiredType) throws PandaException {
        Assert.notNull(requiredType);
        Map<String, Object> stringObjectMap = pandasByClassMap.get(requiredType);
        if (stringObjectMap != null){
            return (Map<String, T>)stringObjectMap;
        }
        List<String> pandaNameList = pandaNamesByClassMap.get(requiredType);
        for (String pandaName : pandaNameList) {
            getPanda(pandaName);
        }
        stringObjectMap = pandasByClassMap.get(requiredType);
        if (stringObjectMap == null){
            throw new PandaException(StrFormatter.format("no matched pandas found with class:{}",requiredType));
        }
        return (Map<String, T>)stringObjectMap;
    }



    private Object doBreed(PandaEmbryo pandaEmbryo){
        PandaEmbryo.EmbryoType embryoType = pandaEmbryo.getEmbryoType();
        Object panda = null;
        if (embryoType == PandaEmbryo.EmbryoType.constructor){
            Constructor constructor = pandaEmbryo.getConstructor();
            Parameter[] parameters = constructor.getParameters();
            Object[] objects = doBreedParametersPanda(parameters);
            panda =  pandaEmbryo.newInstanceByConstructor(objects);
        } else if (embryoType == PandaEmbryo.EmbryoType.method){
            if (pandaEmbryo.getMethod().getParameterCount()>0){
                Parameter[] parameters = pandaEmbryo.getMethod().getParameters();
                Object[] objects = doBreedParametersPanda(parameters);
                Object pandaRoom = getPanda(pandaEmbryo.getPandaRoomClass());
                panda = pandaEmbryo.newInstanceByMethod(pandaRoom, objects);
            }

        }
        Object target = panda;
        if (pandaEmbryo.isProxyTarget()){
            List<String> aspectList = targetAspectMap.get(pandaEmbryo.getName());
            AspectInfo[] aspectInfos = new AspectInfo[aspectList.size()];
            for (int i = 0; i < aspectInfos.length; i++) {
                String s = aspectList.get(i);
                Object aspectPanda = getPanda(s);
                AspectInfo aspectInfo = new AspectInfo((Aspect)aspectPanda,aspectInfoMap.get(s));
                aspectInfos[i] = aspectInfo;
            }
            panda = proxyFactory.proxy(panda, aspectInfos);
        }
        pandaByNameMap.put(pandaEmbryo.getName(),panda);
        Map<String, Object> stringObjectMap = pandasByClassMap.get(pandaEmbryo.getPandaClass());
        if (stringObjectMap == null){
            stringObjectMap = new ConcurrentHashMap<>();
        }
        stringObjectMap.put(pandaEmbryo.getName(),panda);
        pandasByClassMap.put(pandaEmbryo.getPandaClass(),stringObjectMap);
        //属性注入
        Field[] fields = ClassUtil.getDeclaredFields(pandaEmbryo.getPandaClass());
        doBreedFieldPanda(fields,target);
        return panda;
    }

    /**
     * 获取定义的实例的名称
     * @param clazz
     * @return String
     */
    private String getPandaName(Panda panda,Class clazz){
        if (panda != null){
            return panda.name().equals("")?clazz.getCanonicalName():panda.name();
        }
        Assert.notNull(clazz);
        return panda.name().equals("")?clazz.getCanonicalName():panda.name();
    }

    /**
     * 向容器注册实例对象定义
     * @param pandaEmbryo
     */
    private void registerPandaEmbryo(PandaEmbryo pandaEmbryo){
        pandaEmbryoByNameMap.put(pandaEmbryo.getName(), pandaEmbryo);
        List<String> pandaNameList = pandaNamesByClassMap.get(pandaEmbryo.getPandaClass());
        if (pandaNameList == null){
            pandaNameList = new LinkedList<>();
        }
        pandaNameList.add(pandaEmbryo.getName());
        pandaNamesByClassMap.put(pandaEmbryo.getPandaClass(),pandaNameList);
    }

    /**
     * 构造器或者方法实例化时对依赖的实例进行递归实例化
     * @param parameters
     * @return
     */
    private Object[] doBreedParametersPanda(Parameter[] parameters){
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Named named = parameter.getAnnotation(Named.class);
            if (named != null){
                String value = named.value();
                if ("".equals(value)){
                    value = parameter.getClass().getCanonicalName();
                }
                Object o = getPanda(value);
                args[i] = o;
            }else {
                Object o = getPanda(parameter.getClass().getCanonicalName());
                args[i] = o;
            }
        }
        return args;
    }

    /**
     * 对属性所依赖的实例进行实例化
     * @param fields
     */
    private void doBreedFieldPanda(Field[] fields,Object object){
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Inject.class)){
                continue;
            }
            if (field.isAnnotationPresent(Named.class)){
                Named named = field.getAnnotation(Named.class);
                String value = named.value();
                if ("".equals(value)){
                    value = field.getClass().getCanonicalName();
                }
                Object panda = getPanda(value);
                field.setAccessible(true);
                try {
                    field.set(object,panda);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }else {
                Object panda = getPanda(field.getType().getCanonicalName());
                field.setAccessible(true);
                try {
                    field.set(object,panda);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    ///=======================================================================


    /**
     * step 1
     * 收集对象的定义
     * @param basePackages
     */
    public void collectPandaEmbryo(String... basePackages){
        Assert.notEmpty(basePackages);
        for (String basePackage : basePackages) {
            //对配置类进行扫描收集
            Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(basePackage, Panda.class);
            for (Class<?> clazz : classSet) {
                String pandaName = getPandaName(clazz.getAnnotation(Panda.class),clazz);
                Constructor<?>[] constructors = clazz.getConstructors();
                for (Constructor<?> constructor : constructors) {
                    if (constructor.isAnnotationPresent(Inject.class)||constructor.getParameterCount() == 0){
                        PandaEmbryo pandaEmbryo = new PandaEmbryo(pandaName, clazz, constructor);
                        if (clazz.isAnnotationPresent(PointCut.class)&&Aspect.class.isAssignableFrom(clazz)
                                &&!clazz.isAnnotationPresent(PandaRoom.class)){
                            aspectInfoMap.put(pandaName,clazz.getAnnotation(PointCut.class));
                            pandaEmbryo.setAspectTarget(true);
                        }
                        registerPandaEmbryo(pandaEmbryo);
                        break;
                    }
                }
                if (clazz.isAnnotationPresent(PandaRoom.class)){
                    List<Method> methodList = ClassUtil.getPublicMethods(clazz, method -> method.isAnnotationPresent(Panda.class));
                    methodList.forEach(method -> {
                        String pandaName_ = getPandaName(method.getAnnotation(Panda.class), method.getReturnType());
                        PandaEmbryo pandaEmbryo = new PandaEmbryo(pandaName_, clazz, method);
                        if (method.getReturnType().isAnnotationPresent(PointCut.class)
                                &&Aspect.class.isAssignableFrom(method.getReturnType())){
                            aspectInfoMap.put(pandaName_,clazz.getAnnotation(PointCut.class));
                            pandaEmbryo.setAspectTarget(true);
                        }
                        registerPandaEmbryo(pandaEmbryo);
                    });
                }
            }
        }
        completeEmbryo();
    }
    /**
     * 处理对象定义信息，将需要被代理的对象定义信息找出并和对应的切面进行关联
     */

    private void completeEmbryo(){
        for (Map.Entry<String, PointCut> aspectInfoEntry : aspectInfoMap.entrySet()) {
            PointCut pointCut = aspectInfoEntry.getValue();
            for (Map.Entry<String, PandaEmbryo> pandaEmbryoEntry : pandaEmbryoByNameMap.entrySet()) {
                PandaEmbryo pandaEmbryo = pandaEmbryoEntry.getValue();
                if (!pandaEmbryo.isAspectTarget()){
                    //切面不能被代理
                    Class pandaClass = pandaEmbryo.getPandaClass();
                    String[] packages = pointCut.packages();
                    if (Arrays.stream(packages).anyMatch(s -> pandaClass.getPackage().getName().startsWith(s))){
                        Method[] declaredMethods = pandaClass.getDeclaredMethods();
                        for (Method declaredMethod : declaredMethods) {
                            if (declaredMethod.isAnnotationPresent(pointCut.annotation())){
                                pandaEmbryo.setProxyTarget(true);
                                List<String> aspectList = targetAspectMap.get(pandaEmbryo.getName());
                                if (aspectList == null){
                                    aspectList = new ArrayList<>();
                                }
                                aspectList.add(aspectInfoEntry.getKey());
                                targetAspectMap.put(pandaEmbryo.getName(),aspectList);
                                break;
                            }
                        }
                    }
                }
            }
        }
    }
    /**step 2
     * 实例化定义的对象
     */
    public void breedPandas(){
        for (Map.Entry<String, PandaEmbryo> entry : pandaEmbryoByNameMap.entrySet()) {
            getPanda(entry.getKey());
        }
    }


}
